www.gusucode.com > Phased Array System Toolbox Add-On for Demorad 工具箱matlab源码程序 > Phased Array System Toolbox Add-On for Demorad/shared/utilities/RadarBasebandFileWriter.m

    classdef (StrictDefaults)RadarBasebandFileWriter < matlab.System & ...
                          matlab.system.mixin.Propagates & ...
                          matlab.system.mixin.CustomIcon & ... 
                          matlab.system.mixin.internal.SampleTime
%RadarBasebandFileWriter Write baseband signal input into a file
%   BBW = RadarBasebandFileWriter creates a baseband file writer System
%   object, BBW, that writes baseband signals to a binary file. Some
%   descriptive data about the signals will also be written to the file.
%   One frame of signals is written at a time.
%
%   BBW = RadarBasebandFileWriter(Name,Value) creates a baseband file
%   writer System object, BBW, with the specified property Name set to the
%   specified Value. You can specify additional name-value pair arguments
%   in any order as (Name1,Value1,...,NameN,ValueN).
%
%   BBW = RadarBasebandFileWriter(FILENAME,FS,FC,MD,Name,Value) creates a
%   baseband file writer System object, BBW, with the Filename property set
%   to FILENAME, the SampleRate property set to FS, the CenterFrequency
%   property set to FC, the Metadata property set to MD, and other
%   specified property Name set to the specified Value. FILENAME, FS, FC,
%   and MD are value-only arguments. Specify value-only arguments in order.
%   They must precede the name-value pairs. You can specify name-value pair
%   arguments in any order.
%
%   Step method syntax:
%
%   step(BBW, SAMPLES) writes one frame of baseband samples, SAMPLES, to
%   the baseband file specified by the FILENAME property of BBW. The input
%   SAMPLES must be a numeric, 2-D matrix with real or complex values.
%   SAMPLES contains consecutive time samples down each column with one
%   column per signal channel.
%   
%   A baseband file is a specific type of binary file written by
%   RadarBasebandFileWriter. Baseband signals are typically down-converted
%   from a center frequency of FC Hz to 0 Hz (baseband), and sampled at a
%   rate of FS Hz. FS and FC, specified by the SampleRate and
%   CenterFrequency properties respectively, are recorded when a baseband
%   file is created. Additional descriptive data about the signals may be
%   stored in the Metadata property and is written to the baseband file.
%
%   System objects may be called directly like a function instead of using
%   the step method. For example, y = step(obj, x) and y = obj(x) are
%   equivalent.
%
%   RadarBasebandFileWriter methods:
%
%   step     - Write baseband signal input to a file
%   release  - Allow changes to property values and input characteristics.
%              Write remaining signals in the baseband file writer object
%              to the file, and release baseband file writer resources
%   clone    - Create baseband file writer object with same property values
%              in an unlocked status
%   isLocked - Locked status (logical)
%   reset    - Reset to the beginning of the baseband file
%   info     - Returns information about the baseband file writer
% 
%   RadarBasebandFileWriter properties:
%
%   Filename          - Baseband file name
%   SampleRate        - Sample rate (Hz) information for baseband signal
%   CenterFrequency   - Center frequency (Hz) information for baseband signal
%   Metadata          - Additional descriptive data about baseband signal
%   NumSamplesToWrite - Maximum number of latest samples to write

% Copyright 2019 The MathWorks, Inc.

%#codegen
 
properties (Nontunable)
    %Filename Baseband file name 
    %   Specify the name of the baseband file as a character row vector.
    %   The file name can include a relative or absolute path. The default
    %   value of this property is 'untitled.bb'.
    Filename = 'untitled.bb'
    %SampleRate Sample rate (Hz)
    %   Specify the sample rate for the baseband signal in Hz as a double
    %   precision, real, positive scalar. The default value of this
    %   property is 1 Hz.
    SampleRate = 1
    %CenterFrequency Center frequency (Hz)
    %   Specify the center frequency(ies) for the baseband signal in Hz as
    %   a double precision, real, scalar or row vector with length equal to
    %   the input number of channels. The default value of this property is
    %   1e8 Hz.
    CenterFrequency = 1e8
    %Metadata Metadata in a structure
    %   Specify any additional descriptive data for the baseband signal as
    %   a structure scalar. The structure's field values can be of any
    %   numeric, logical or character data type with any dimension. The
    %   default value of this property is an empty structure struct().
    Metadata = struct()
    %NumSamplesToWrite Number of latest samples to write
    %   Specify the maximum number of latest samples to write as inf or a
    %   double precision, real, positive integer scalar. When it is set to
    %   inf, all the input samples are stored in the file. The default
    %   value of this property is inf.
    NumSamplesToWrite = inf    
end

properties (Access = private, Nontunable)
    pFullFilename
    pNumBytesPerSamp
    pSamplesPerFrame
    pNumChannels
    pInputDataType
    pIsCacheNeeded
    pNumFrmInCache
    pIsWritingAll
end

properties (Access = private)
    pNumBytesInHeader
    pFID
    pCache
    pFrmIdxInCache
    pNumFrmWritten
end

properties(Constant, Hidden)    
    MaxCacheSizeInfinite = 1024*1024;      % Maximum 1MB cache when writing all
    MaxCacheSizeFinite   = 100*1024*1024;  % Maximum 100MB cache when retaining latest
    VersionInfo          = 'BasebandVer1.0.0    ';
end
    
methods
  function obj = RadarBasebandFileWriter(varargin)
    setProperties(obj,nargin,varargin{:}, ...
        'Filename','SampleRate','CenterFrequency','Metadata');
  end

  function set.Filename(obj, fname)
    
    propName = 'Filename';
    fname = convertStringsToChars(fname);
    validateattributes(fname, {'char'}, {'row','nonempty'}, ...
        [class(obj) '.' propName], propName);
    
    RadarBasebandFileWriter.validateNameAndExt(fname);
    
    obj.Filename = fname;
  end

  function set.SampleRate(obj, fs)
    propName = 'SampleRate';
    validateattributes(fs,{'double'}, ...
        {'scalar','real','positive','finite'}, ...
        [class(obj) '.' propName], propName);
    
    obj.SampleRate = fs;
  end
        
  function set.CenterFrequency(obj, fc)
    propName = 'CenterFrequency';
    validateattributes(fc,{'double'}, ...
        {'row','real','finite'}, ...
        [class(obj) '.' propName], propName);
    
    obj.CenterFrequency = fc;
  end
  
  function set.Metadata(obj, mData)
    propName = 'Metadata';
    validateattributes(mData,{'struct'}, {'scalar'}, ...
        [class(obj) '.' propName], propName);

    metaFields = fieldnames(mData);    
    for i = 1:length(metaFields)
        validateattributes(mData.(metaFields{i}),...
            {'numeric','logical','char'}, {'nonsparse','nonempty'}, ...
            [class(obj) '.' propName], [metaFields{i}, ' field of Metadata']);        
    end
    
    obj.Metadata = mData;
  end
  
  function set.NumSamplesToWrite(obj, val)
    if ~any(isinf(val)) || ~isscalar(val)
        propName = 'NumSamplesToWrite';
        validateattributes(val,{'double'}, ...
            {'scalar','real','integer','positive'}, ...
            [class(obj) '.' propName], propName);
    end
    
    obj.NumSamplesToWrite = val;
  end
end

methods (Access = protected)
  function validateInputsImpl(obj, x)
    validateattributes(x, {'numeric'}, ...
        {'nonsparse','2d','nonempty','finite'}, class(obj), 'input');    
  end
  
  function setupImpl(obj, x)           
    coder.extrinsic('RadarBasebandFileWriter.getFullFileName', ...
                    'RadarBasebandFileWriter.getNumBytesPerSample');
    
    obj.pFullFilename = coder.const(...
        RadarBasebandFileWriter.getFullFileName(obj.Filename));
    
    % Save data attributes    
    obj.pSamplesPerFrame = size(x, 1);
    obj.pNumChannels     = size(x, 2);
    obj.pInputDataType   = class(x);
    obj.pNumBytesPerSamp = coder.const( ...
        RadarBasebandFileWriter.getNumBytesPerSample(obj.pInputDataType, true));
    
    if length(obj.CenterFrequency) > 1 && ...
        length(obj.CenterFrequency) ~= obj.pNumChannels, ...
        error('The number of input channels must equal the length of the CenterFrequency vector');
    end

    if ~isinf(obj.NumSamplesToWrite) && ...
        obj.NumSamplesToWrite > obj.MaxCacheSizeFinite/obj.pNumBytesPerSamp/obj.pNumChannels
        error('NumSamplesToWrite must be inf or less than or equal to %d, given the current input dimension and data type.', ...
        floor(obj.MaxCacheSizeFinite/obj.pNumBytesPerSamp/obj.pNumChannels));
    end
    
    % Initialize file and write header
    initFileAndWriteHeader(obj);     

    % Initialize cache
    initCache(obj);

    if ~isempty(coder.target)
        % Assign properties used in releaseImpl (codegen requirement)
        resetProperties(obj);
    end
  end
  
  function resetProperties(obj)
    % Reset counters
    obj.pFrmIdxInCache = 0; 
    obj.pNumFrmWritten = 0;

    % Reset cache
    if obj.pIsCacheNeeded
        obj.pCache = complex(zeros( ...
            [obj.pSamplesPerFrame, obj.pNumChannels, obj.pNumFrmInCache], obj.pInputDataType));
    end
  end

  function resetImpl(obj)
    % Skip header and go to the beginning of data part
    fseek(obj.pFID, obj.pNumBytesInHeader, 'bof');
    
    resetProperties(obj);
  end
  
  function stepImpl(obj, x)    
    if obj.pIsWritingAll && ~obj.pIsCacheNeeded
        % Write input to file directly
        rowMajorFileWritting(obj, x);
    else
        frmIdxInCache = obj.pFrmIdxInCache;
        
        % Fill cache
        if isreal(x)
            obj.pCache(:, :, frmIdxInCache+1) = complex(x);
        else
            obj.pCache(:, :, frmIdxInCache+1) = x;
        end
        
        % Update frame index in cache, avoid using "mod" for performance
        if frmIdxInCache == obj.pNumFrmInCache - 1
            obj.pFrmIdxInCache = 0;
        else
            obj.pFrmIdxInCache = frmIdxInCache + 1;
        end
        
        % Write full cache to file
        if obj.pIsWritingAll && (obj.pFrmIdxInCache == 0)
            rowMajorFileWritting(obj, obj.pCache);
        end
    end
    
    obj.pNumFrmWritten = obj.pNumFrmWritten + 1;
  end

  function releaseImpl(obj)
    numFrmInCache = obj.pNumFrmInCache;
    frmIdxInCache = obj.pFrmIdxInCache;
    numFrmWritten = obj.pNumFrmWritten;
    
    if ~obj.pIsWritingAll
        if numFrmWritten < numFrmInCache
            % When # of samples written is less than NumSamplesToWrite,
            % write everything in cache to the file and done.
            rowMajorFileWritting(obj, obj.pCache(:,:,1:frmIdxInCache));
        else 
            % First write partial or all samples from the oldest frame in cache
            numRemSamp = obj.NumSamplesToWrite - (numFrmInCache - 1) * obj.pSamplesPerFrame;
            rowMajorFileWritting(obj, obj.pCache(end-numRemSamp+1:end,:,frmIdxInCache+1));
            % Then write all the rest frames in cache
            frmIdxToWrite = mod(frmIdxInCache + (1:numFrmInCache-1), numFrmInCache) + 1;
            rowMajorFileWritting(obj, obj.pCache(:,:,frmIdxToWrite));
        end        
    elseif (obj.pIsCacheNeeded) && (obj.pFrmIdxInCache > 0)
        % Append remaining frames in cache to the file
        rowMajorFileWritting(obj, obj.pCache(:,:,1:frmIdxInCache));        
    end
    
    % Close file for writing
    fclose(obj.pFID);
    obj.pFID = -1;
  end

  function s = infoImpl(obj)    
    %info Returns characteristic information about the baseband file writer
    %   S = info(BBW) returns a structure containing characteristic
    %   information, S, about the baseband file writer. A description of the
    %   fields and their values is as follows:
    % 
    %   Filename          - The baseband file name with full path
    %   SamplesPerFrame   - The input samples per frame, applicable only 
    %                       when the object is locked
    %   NumChannels       - The input number of channels, applicable only 
    %                       when the object is locked
    %   DataType          - The input data type, applicable only when the 
    %                       object is locked
    %   NumSamplesWritten - The total number of input samples that have
    %                       been written to the file. It cannot exceed the
    %                       NumSamplesToWrite property value. 
    
    coder.extrinsic('RadarBasebandFileWriter.getFullFileName');
    if ~isLocked(obj)
        % Have no information to provide other than the filename when the
        % object is unlocked
        s.Filename = coder.const(RadarBasebandFileWriter.getFullFileName(obj.Filename));
        s.NumSamplesWritten = 0;
    else
        s.Filename          = obj.pFullFilename;
        s.SamplesPerFrame   = obj.pSamplesPerFrame;
        s.NumChannels       = obj.pNumChannels;
        s.DataType          = obj.pInputDataType;
        s.NumSamplesWritten = min(obj.pNumFrmWritten * obj.pSamplesPerFrame, ...
                              obj.NumSamplesToWrite);
    end
  end
  
  function s = saveObjectImpl(obj)
    s = saveObjectImpl@matlab.System(obj);
    s.SaveLockedData = false; % Always save the object unlocked
  end
  
  function groups = getPropertyGroups(~)
    groups = matlab.mixin.util.PropertyGroup({'Filename','SampleRate',...
        'CenterFrequency', 'Metadata', 'NumSamplesToWrite'});      
  end

  function numIn = getNumInputsImpl(~)
      numIn = 1;
  end
  
  function flag = isInputSizeLockedImpl(~,~)
     flag = true;
  end  
  
  function flag = isInputComplexityLockedImpl(~,~)
    flag = false;
  end
  
  % For MATLAB System block
  function varargout = getInputNamesImpl(~)      
    varargout = {''};
  end
  
  function icon = getIconImpl(~)
    icon = sprintf('Radar Baseband\nFile\nWriter');
  end    
end

methods (Access = private)    
  function initFileAndWriteHeader(obj)
    % Open file for writing
    if isempty(coder.target)
        [obj.pFID, errMsg] = fopen(obj.pFullFilename, 'w');
        if obj.pFID == -1 && ~isempty(errMsg)
            error('Unable to open baseband file: %s.',obj.pFullFilename)
        end
    else
        obj.pFID = fopen(obj.pFullFilename, 'w');
        if obj.pFID == -1
            error('Unable to open baseband file.');
        end
    end
    

    fs = obj.SampleRate;

    
    % Reserve 20 char for version information
    fwrite(obj.pFID, obj.VersionInfo, 'char');    
    
    % Write sample rate into header
    fwrite(obj.pFID, fs, 'double');
    
    % Write center frequency into header
    fwrite(obj.pFID, size(obj.CenterFrequency), 'uint32');
    fwrite(obj.pFID, obj.CenterFrequency, 'double');
    
    % Write metadata into header
    metaFields = fieldnames(obj.Metadata);
    numMetaFields = length(metaFields);            
    fwrite(obj.pFID, numMetaFields, 'uint32'); % Number of fields in metadata
    
    for i = 1:numMetaFields % Write each metadata field one-by-one
        thisValue = obj.Metadata.(metaFields{i});
        thisClass = class(thisValue); 
        fwrite(obj.pFID, length(metaFields{i}),     'uint32' ); % Field: Length
        fwrite(obj.pFID, metaFields{i},             'char'   ); % Field: Name
        fwrite(obj.pFID, getDataTypeID(thisClass),  'uint8'  ); % Value: Data type 
        fwrite(obj.pFID, uint8(isreal(thisValue)),  'uint8'  ); % Value: Complexity
        fwrite(obj.pFID, ndims(thisValue),          'uint8'  ); % Value: Num of dim 
        fwrite(obj.pFID, size(thisValue),           'uint32' ); % Value: Dim
        if isa(thisValue, 'logical')
            classToWrite = 'uint8';
        else
            classToWrite = thisClass;
        end
        fwrite(obj.pFID, real(thisValue),           classToWrite); % Value: Real part
        if ~isreal(thisValue)
            fwrite(obj.pFID, imag(thisValue),       classToWrite); % Value: Imaginary part
        end
    end

    % Write data information into header
    fwrite(obj.pFID, getDataTypeID(obj.pInputDataType), 'uint8');
    fwrite(obj.pFID, obj.pNumBytesPerSamp,              'uint32');
    fwrite(obj.pFID, obj.pSamplesPerFrame,              'uint32');
    fwrite(obj.pFID, obj.pNumChannels,                  'uint32');
    
    % Store current file pointer, which is where data part begins
    obj.pNumBytesInHeader = ftell(obj.pFID);
  end

  function initCache(obj)    
    sampPerFrm = obj.pSamplesPerFrame;
    numChan    = obj.pNumChannels;
    
    % Determine # of frames in cache
    obj.pIsWritingAll = isinf(obj.NumSamplesToWrite);
    if obj.pIsWritingAll
        obj.pNumFrmInCache = floor(obj.MaxCacheSizeInfinite/...
            obj.pNumBytesPerSamp/numChan/sampPerFrm); % >= 0
    else % Hold all the data in cache until release call
        obj.pNumFrmInCache = ceil(obj.NumSamplesToWrite /sampPerFrm); % >= 1
    end
    obj.pIsCacheNeeded  = ~obj.pIsWritingAll || (obj.pNumFrmInCache >= 2);
  end
  
  function rowMajorFileWritting(obj, data)
    % Most efficient way to write cache into file in a row major fashion
    if ~isempty(data)
        count = fwrite(obj.pFID, cat(1, permute(real(data), [2 1 3]), ...
                                        permute(imag(data), [2 1 3])), obj.pInputDataType);
        if count < 2*numel(data), ...
            error('Data lost in file writing.');
        end
    end
  end
end

methods(Static, Access = protected)
  function header = getHeaderImpl
    header = matlab.system.display.Header('RadarBasebandFileWriter', ...
        'ShowSourceLink', true, ... 
        'Title', 'Baseband File Writer', 'Text', ...
        'Write baseband signal input into a file.');  
  end
 
  function group = getPropertyGroupsImpl
    param = matlab.system.display.Section(...
        'Title', 'Parameters',...
        'PropertyList', {'Filename','CenterFrequency', ...
        'Metadata', 'NumSamplesToWrite'});  
    
    browseAction = matlab.system.display.Action(@RadarBasebandFileWriter.onBrowseActionCalled, ...
        'Label', 'Browse...','Placement','CenterFrequency','Alignment', 'right');            
    param.Actions = browseAction;
    
    group = param;       
  end
  
  function onBrowseActionCalled(actionData, ~)
    [fname, pathname] = uiputfile('*.bb', 'Pick a baseband file');
    if ischar(fname) 
        fullname = fullfile(pathname, fname);

        h = actionData.SystemHandle;
        if isa(h, 'matlab.System')
            h.Filename = fullname;
        else
            set_param(h, 'Filename', fullname);
        end 
    end
  end  
end

methods(Static, Hidden)
  function validateNameAndExt(fname)
    [~, name, ext] = fileparts(fname);        

    % Check file name
    if ~isempty(regexp(name, '[/\*:?"<>|]', 'once')) ||  ...
        isempty(name)
      error('Invalid file name.');
    end
        
    % Check file extension
    if ~isempty(regexp(ext, '[/\*:?"<>|]', 'once'))
        error('Invalid file extension.');  
    end
  end      
  
  function fullName = getFullFileName(fname)
    [pathStr, name, ext] = fileparts(fname);

    if ~isempty(pathStr)
        % Check path
        if ~isdir(pathStr), ...
            error('Path %s does not exist.',pathStr);
        end
        wd = cd(pathStr); 
        fullName = [pwd filesep name ext]; 
        cd(wd);
    else
        fullName = [pwd filesep name ext]; 
    end   
  end
  
  function numBytes = getNumBytesPerSample(dataType, isDataComplex)
    temp = ones(1, 1, dataType); %#ok<NASGU>
    s = whos('temp');
    numBytes = s.bytes * (1 + isDataComplex);
  end
end

end

% Local helper functions
function id = getDataTypeID(dType)
  switch dType
    case 'double'
      id = uint8(1);
    case 'single'
      id = uint8(2);
    case 'int8'
      id = uint8(3);
    case 'int16'
      id = uint8(4);
    case 'int32'
      id = uint8(5);
    case 'int64'
      id = uint8(6);
    case 'uint8'
      id = uint8(7);
    case 'uint16'
      id = uint8(8);
    case 'uint32'
      id = uint8(9);
    case 'uint64'
      id = uint8(10);
    case 'logical'
      id = uint8(11);
    otherwise % 'char'
      id = uint8(12);
  end 
end

% [EOF]